package org.columba.mail.gui.socialnet.selection;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;

import javax.swing.JComponent;

import org.columba.addressbook.gui.dialog.contact.ContactEditorDialog;
import org.columba.api.command.ICommandReference;
import org.columba.api.gui.frame.IContainer;
import org.columba.api.gui.frame.IFrameMediator;
import org.columba.core.gui.dialog.ErrorDialog;
import org.columba.core.gui.frame.FrameManager;
import org.columba.core.logging.Logging;
import org.columba.core.selection.SelectionHandler;
import org.columba.mail.command.MailFolderCommandReference;
import org.columba.mail.folder.IMailFolder;
import org.columba.mail.gui.socialnet.SocialNetWorkActorVertex;
import org.columba.mail.gui.socialnet.SocialRoot;

import com.deruan.hotcore.contact.model.ContactModelFactory;
import com.deruan.hotcore.contact.model.IContactModel;
import com.deruan.hotcore.resource.model.IResourceModel;
import com.deruan.hotcore.resource.model.ResourceModelFactory;
import com.deruan.hotcore.util.StringUtil;

import edu.uci.ics.jung.graph.Edge;
import edu.uci.ics.jung.graph.Vertex;
import edu.uci.ics.jung.graph.impl.SparseVertex;
import edu.uci.ics.jung.visualization.Layout;
import edu.uci.ics.jung.visualization.PickSupport;
import edu.uci.ics.jung.visualization.PickedState;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.VisualizationViewer.Paintable;

public class VertexSelectionHandler extends SelectionHandler 
             implements MouseListener,MouseMotionListener {
	
	private VisualizationViewer view;
	
	public static final String HANDLER_ID = "mail.socialnet";
	
	private boolean isSelect = false;
	
	private IMailFolder selectedFolder;
	
	private List<Vertex> vertexSelectedList = new ArrayList<Vertex>();
	
	/**
	 * the picked Vertex, if any
	 */
    protected Vertex vertex = null;
    
    /**
     * the picked Edge, if any
     */
    protected Edge edge;
    
    /**
     * the x distance from the picked vertex center to the mouse point
     */
    protected double offsetx;
    
    /**
     * the y distance from the picked vertex center to the mouse point
     */
    protected double offsety;
    
    /**
     * controls whether the Vertices may be moved with the mouse
     */
    protected boolean locked;
    
    /**
     * additional modifiers for the action of adding to an existing
     * selection
     */
    protected int addToSelectionModifiers;
    
    /**
     * used to draw a rectangle to contain picked vertices
     */
    protected Rectangle2D rect = new Rectangle2D.Float();
    
    /**
     * the Paintable for the lens picking rectangle
     */
    protected Paintable lensPaintable;
    
    /**
     * color for the picking rectangle
     */
    protected Color lensColor = Color.cyan;
    /**
	 * modifiers to compare against mouse event modifiers
	 */
    protected int modifiers;
    
    /**
     * the location in the View where the mouse was pressed
     */
    protected Point down;
    
    /**
     * the special cursor that plugins may display
     */
    protected Cursor cursor;
	
	
	public VertexSelectionHandler(VisualizationViewer view)
	{
		super(VertexSelectionHandler.HANDLER_ID);
		this.view = view;
		view.addMouseListener(this);
		view.addMouseMotionListener(this);
		this.modifiers = InputEvent.BUTTON1_MASK;
		this.addToSelectionModifiers = InputEvent.BUTTON1_MASK | InputEvent.SHIFT_MASK;
        this.lensPaintable = new LensPaintable();
        this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
	}
    
    public ICommandReference getSelection() {
		if (selectedFolder == null)
			return null;
		MailFolderCommandReference reference = new MailFolderCommandReference(selectedFolder);
		return reference;
	}

	public void setSelection(ICommandReference selection) 
	{
		MailFolderCommandReference ref = (MailFolderCommandReference) selection;

		selectedFolder = (IMailFolder) ref.getSourceFolder();
	}

	/*
    public void mousePressed(MouseEvent e) 
    {
    	if(e.getButton()==1)
    	{
    		Point2D p = view.inverseViewTransform(e.getPoint());
        	SocialNetWorkActorVertex v = (SocialNetWorkActorVertex)view.getPickSupport().getVertex(p.getX(), p.getY());
            if (v == null) 
            {
            	return;
            }
            else if(v.equals(picked))
            {
            	return;
            }else
            {
            	picked = v;
            	System.out.println("contact is "+v.getName());
            	IMailFolder folder = ContactsManager.getContactsManager().getMailFolderMap().get(v.getName());
            	selectedFolder = folder;
            	fireSelectionChanged(new VertexSelectionChangedEvent(folder));
            }     
    	}   	
      }
      */
    
    /**
     * @return Returns the lensColor.
     */
    public Color getLensColor() {
        return lensColor;
    }

    /**
     * @param lensColor The lensColor to set.
     */
    public void setLensColor(Color lensColor) {
        this.lensColor = lensColor;
    }

    /**
     * a Paintable to draw the rectangle used to pick multiple
     * Vertices
     * @author Tom Nelson - RABA Technologies
     *
     */
    class LensPaintable implements Paintable {

        public void paint(Graphics g) {
            Color oldColor = g.getColor();
            g.setColor(lensColor);
            ((Graphics2D)g).draw(rect);
            g.setColor(oldColor);
        }

        public boolean useTransform() {
            return false;
        }
    }

	/**
	 * For primary modifiers (default, MouseButton1):
	 * pick a single Vertex or Edge that
     * is under the mouse pointer. If no Vertex or edge is under
     * the pointer, unselect all picked Vertices and edges, and
     * set up to draw a rectangle for multiple selection
     * of contained Vertices.
     * For additional selection (default Shift+MouseButton1):
     * Add to the selection, a single Vertex or Edge that is
     * under the mouse pointer. If a previously picked Vertex
     * or Edge is under the pointer, it is un-picked.
     * If no vertex or Edge is under the pointer, set up
     * to draw a multiple selection rectangle (as above)
     * but do not unpick previously picked elements.
	 * 
	 * @param e the event
	 */
    public void mousePressed(MouseEvent e) 
    {
        down = e.getPoint();
        PickSupport pickSupport = view.getPickSupport();
        PickedState pickedState = view.getPickedState();
        if(pickSupport != null && pickedState != null) 
        {
            Layout layout = view.getGraphLayout();
            if(e.getModifiers() == modifiers) 
            {
            	view.addPostRenderPaintable(lensPaintable);
                rect.setFrameFromDiagonal(down,down);
                // p is the screen point for the mouse event
                Point2D p = e.getPoint();
                // take away the view transform
                Point2D ip = view.inverseViewTransform(p);
                
                vertex = pickSupport.getVertex(ip.getX(), ip.getY());
                if(vertex != null) 
                {
                    if(pickedState.isPicked(vertex) == false) 
                    {
                        pickedState.clearPickedVertices();
                        pickedState.pick(vertex, true);
                        isSelect = true;
                        //System.out.println("contact is "+((SocialNetWorkActorVertex)vertex).getName());
                        IMailFolder folder =((SocialNetWorkActorVertex)vertex).getFolder() ;
                        selectedFolder = folder;
                        fireSelectionChanged(new VertexSelectionChangedEvent(folder)); 
                    }
                    // layout.getLocation applies the layout transformer so
                    // q is transformed by the layout transformer only
                    Point2D q = layout.getLocation(vertex);
                    // transform the mouse point to graph coordinate system
                    Point2D gp = view.inverseLayoutTransform(ip);
                    offsetx = (float) (gp.getX()-q.getX());
                    offsety = (float) (gp.getY()-q.getY());
                } else if((edge = pickSupport.getEdge(ip.getX(), ip.getY())) != null) {
                    pickedState.clearPickedEdges();
                    pickedState.pick(edge, true);
                    fireSelectionChanged(new VertexSelectionChangedEvent()); 
                } else {
                    pickedState.clearPickedEdges();
                    pickedState.clearPickedVertices();
                    fireSelectionChanged(new VertexSelectionChangedEvent()); 
                }
                
            } else if(e.getModifiers() == addToSelectionModifiers) {
            	view.addPostRenderPaintable(lensPaintable);
                rect.setFrameFromDiagonal(down,down);
                Point2D p = e.getPoint();
                // remove view transform
                Point2D ip = view.inverseViewTransform(p);
                vertex = pickSupport.getVertex(ip.getX(), ip.getY());
                if(vertex != null) {
                    boolean wasThere = pickedState.pick(vertex, !pickedState.isPicked(vertex));
                    if(wasThere) {
                        vertex = null;
                    } else {

                        // layout.getLocation applies the layout transformer so
                        // q is transformed by the layout transformer only
                        Point2D q = layout.getLocation(vertex);
                        // translate mouse point to graph coord system
                        Point2D gp = view.inverseLayoutTransform(ip);

                        offsetx = (float) (gp.getX()-q.getX());
                        offsety = (float) (gp.getY()-q.getY());
                    }
                } else if((edge = pickSupport.getEdge(ip.getX(), ip.getY())) != null) {
                    pickedState.pick(edge, !pickedState.isPicked(edge));
                }
            }
        }
        if(vertex != null) e.consume();
    }

    /**
	 * If the mouse is dragging a rectangle, pick the
	 * Vertices contained in that rectangle
	 * 
	 * clean up settings from mousePressed
	 */
    public void mouseReleased(MouseEvent e) {
        if(e.getModifiers() == modifiers) {
            if(down != null) {
                Point2D out = e.getPoint();
                if(vertex == null && heyThatsTooClose(down, out, 5) == false) {
                    pickContainedVertices(view, true);
                }
            }
        } else if(e.getModifiers() == this.addToSelectionModifiers) {
            if(down != null) {
                Point2D out = e.getPoint();
                if(vertex == null && heyThatsTooClose(down,out,5) == false) {
                    pickContainedVertices(view, false);
                }
            }
        }
        down = null;
       // vertex = null;
        edge = null;
        rect.setFrame(0,0,0,0);
        view.removePostRenderPaintable(lensPaintable);
    }
    
    /**
	 * If the mouse is over a picked vertex, drag all picked
	 * vertices with the mouse.
	 * If the mouse is not over a Vertex, draw the rectangle
	 * to select multiple Vertices
	 * 
	 */
    public void mouseDragged(MouseEvent e) {
        if(locked == false) {
            VisualizationViewer vv = (VisualizationViewer)e.getSource();
            if(vertex != null) {
                Point p = e.getPoint();
                Point2D graphPoint = vv.inverseTransform(p);
                Point2D graphDown = vv.inverseTransform(down);
                Layout layout = vv.getGraphLayout();
                double dx = graphPoint.getX()-graphDown.getX();
                double dy = graphPoint.getY()-graphDown.getY();
                PickedState ps = vv.getPickedState();
                
                for(Iterator iterator=ps.getPickedVertices().iterator(); iterator.hasNext(); ) {
                    Vertex v = (Vertex)iterator.next();
                    Point2D vp = layout.getLocation(v);
                    layout.forceMove(v, vp.getX()+dx, vp.getY()+dy);
                }
                down = p;

            } else {
                Point2D out = e.getPoint();
                if(e.getModifiers() == this.addToSelectionModifiers ||
                        e.getModifiers() == modifiers) {
                    rect.setFrameFromDiagonal(down,out);
                }
            }
            if(vertex != null) e.consume();
        }
    }
    
    /**
     * rejects picking if the rectangle is too small, like
     * if the user meant to select one vertex but moved the
     * mouse slightly
     * @param p
     * @param q
     * @param min
     * @return
     */
    private boolean heyThatsTooClose(Point2D p, Point2D q, double min) {
        return Math.abs(p.getX()-q.getX()) < min &&
                Math.abs(p.getY()-q.getY()) < min;
    }
    
    /**
     * pick the vertices inside the rectangle
     *
     */
    protected void pickContainedVertices(VisualizationViewer vv, boolean clear) {
        
        Layout layout = vv.getGraphLayout();
        vertexSelectedList.clear();
        PickedState pickedState = vv.getPickedState();
        if(pickedState != null) {
            if(clear) {
                pickedState.clearPickedVertices();
            }
            while(true) {
                try {
                	Iterator iter=layout.getGraph().getVertices().iterator();
                    while(iter.hasNext()) {
                        Vertex v = (Vertex) iter.next();
                        if(rect.contains(vv.transform(layout.getLocation(v)))) {
                            pickedState.pick(v, true);
                            vertexSelectedList.add(v);
                        }
                    }
                    break;
                } catch(ConcurrentModificationException cme) {}
            }
            if(vertexSelectedList.size()>0)
            {
                //i add
                fireSelectionChanged(new VertexSelectionChangedEvent(vertexSelectedList)); 
            }
        }
    }

    public void mouseClicked(MouseEvent e) 
    {
        //if mouse button was pressed twice times
		if (e.getClickCount() == 2) 
		{
			if(vertex!=null)
			{
				boolean isRoot = (vertex instanceof SocialRoot);
				String from = ((SocialNetWorkActorVertex)vertex).getFolder().getName();
				IContainer[] container = FrameManager.getInstance().getOpenFrames();
				if (container == null || container.length == 0)
					throw new RuntimeException("No frames available");
				IFrameMediator frameMediator = container[0].getFrameMediator();
				// 
				ContactEditorDialog dialog = 
					new ContactEditorDialog(frameMediator,from,isRoot);

				if (dialog.getResult()) 
				{
					try 
					{
						if(isRoot)
						{
							ContactModelFactory.getInstance().modifyRootContact(dialog.getDestModel(),isRoot);
						}
					} catch (Exception e1) 
					{
						if (Logging.DEBUG)
							e1.printStackTrace();
						ErrorDialog.createDialog(e1.getMessage(), e1);
					}
				}
			}
		}
    }

    public void mouseEntered(MouseEvent e) {
        JComponent c = (JComponent)e.getSource();
        c.setCursor(cursor);
    }

    public void mouseExited(MouseEvent e) {
        JComponent c = (JComponent)e.getSource();
        c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }

    public void mouseMoved(MouseEvent e) {
    }

    /**
     * @return Returns the locked.
     */
    public boolean isLocked() {
        return locked;
    }

    /**
     * @param locked The locked to set.
     */
    public void setLocked(boolean locked) {
        this.locked = locked;
    }
    
    /**
     * getter for mouse modifiers
     */
    public int getModifiers() {
        return modifiers;
    }

    /**
     * setter for mouse modifiers
     */
    public void setModifiers(int modifiers) {
        this.modifiers = modifiers;
    }
    
    /**
     * check the mouse event modifiers against the
     * instance member modifiers. Default implementation
     * checks equality. Can be overridden to test with a mask
     */
    public boolean checkModifiers(MouseEvent e) {
        return e.getModifiers() == modifiers;
    }

    /**
     * @return Returns the cursor.
     */
    public Cursor getCursor() {
        return cursor;
    }

    /**
     * @param cursor The cursor to set.
     */
    public void setCursor(Cursor cursor) {
        this.cursor = cursor;
    }
}
